﻿(*
#r "System.Xml"
#r "System.Xml.Linq"
*)

namespace Octopus.VMDeployer

open System.Collections.Generic
open System.Xml.Linq



type ConfigurationData (vmGuid, machineName, ownerName, adminPassword, securityToken, octopusServiceAddresses) =
  inherit BaseConfigurationData (vmGuid, machineName, ownerName, adminPassword, securityToken, octopusServiceAddresses)

  let actions = new List<Action>()
  let mutable logOutput : System.IO.TextWriter = null

  member x.AddAction action = actions.Add(action)

  //-----------------

  member private x.Step1 () =
    fprintfn logOutput "Functions.changeAccountPassword Administrator ********"
    Functions.changeAccountPassword "Administrator" x.AdminPassword

    fprintfn logOutput "Functions.renameComputername %s" x.MachineName
    Functions.renameComputername x.MachineName

    fprintf logOutput "Functions.octopus_upgradeNic %s %s [| " (x.VmGuid.ToString()) (x.SecurityToken.ToString())
    for a in x.OctopusServiceAddresses do fprintf logOutput "%s; " a
    fprintfn logOutput " |]"
    Functions.octopus_upgradeNic (x.VmGuid) (x.SecurityToken) x.OctopusServiceAddresses

    x.RebootAction <- fun() -> try Functions.win32Shutdown Functions.Win32ShutdownFlags.Shutdown
                               with _ -> Functions.win32Shutdown Functions.Win32ShutdownFlags.ShutdownForced
    
    if actions.Count > 0 then x.DeployingState <- (0,0)  // execute actions
    else x.DeployingState <- (-2,0)                      // execute Step2
    let i,s = x.DeployingState in fprintfn logOutput "State transited to (%d,%d)" i s

  member private x.Step2 () =
    fprintf logOutput "Functions.octopus_notifyVmConfigured %s %s \"\" [| " (x.VmGuid.ToString()) (x.SecurityToken.ToString())
    for a in x.OctopusServiceAddresses do fprintf logOutput "%s; " a
    fprintfn logOutput " |]"
    let message = sprintf "%s ready to be used" x.MachineName
    Functions.octopus_notifyVmConfigured x.VmGuid x.SecurityToken x.OctopusServiceAddresses message

    x.RebootAction <- id
    x.DeployingState <- (-3,-1)   // end
    let i,s = x.DeployingState in fprintfn logOutput "State transited to (%d,%d)" i s
    
  member private x.ExecuteActions () =
    // scan the list [index..last] to execute the actions
    // list empty -> end to execute actions
    // a::t -> execute a; if ends (s=0) scan t; else break and save xml
    let rec scanList (actionsList:Action list) state =
        match actionsList with
        | [] -> x.DeployingState <- (-2,0)        // execute Step2
        | a::t -> match a.ExecuteAction x (snd x.DeployingState) with
                  | (0,_) -> scanList t 0       // continue with the next element
                  | (s,f) -> x.DeployingState <- (actions.IndexOf(a), s)  // break with (index,s) to return here
                             let i,s = x.DeployingState in fprintfn logOutput "State transited to (%d,%d)" i s
                             x.RebootAction <- f                          // and set the reboot action
  
    let index,innerState = x.DeployingState
    let actionList = [ for i = index to (actions.Count-1) do yield actions.[i] ]
    if actionList = [] then failwithf "Invalid state (%d,%d)" index innerState
    scanList actionList innerState
          
  
  member private x._ExecuteDeploy () =
    fprintfn logOutput "------- Executing state %d:%d" (fst x.DeployingState) (snd x.DeployingState)
  
    match x.DeployingState with
    | -1,0 -> x.Step1()
    | -2,0 -> x.Step2()
    | -3,_ -> ()
    | i,s when i<0 -> failwith "Invalid state (%d,%d)" i s
    | _ -> x.ExecuteActions ()    
           // after ExecuteActions, if the state is (-2, 0), finish the deployment
           if x.DeployingState = (-2,0) then x.Step2()

           
  override x.ExecuteDeploy (logFile:System.IO.TextWriter) =
    logOutput <- logFile
    try
      x._ExecuteDeploy ()
    with e ->
      fprintfn logOutput "-- Exception: %s" e.Message
      fprintfn logOutput "%s" e.StackTrace
      fprintfn logOutput "-------------"
      try
        fprintf logOutput "Functions.octopus_notifyConfigurationFailed %s %s \"\" [| " (x.VmGuid.ToString()) (x.SecurityToken.ToString())
        for a in x.OctopusServiceAddresses do fprintf logOutput "%s; " a
        fprintfn logOutput " |]"
        let message = sprintf "%s configuration failed" x.MachineName
        Functions.octopus_notifyConfigurationFailed x.VmGuid x.SecurityToken x.OctopusServiceAddresses message
      with e2 ->
        fprintfn logOutput "Failed to notify the configuration failure, too!!"
        fprintfn logOutput "  -> %s" e2.Message
      reraise()

  //-----------------
  
  member private x.ToXmlDoc () =
    let octopusAddresses = [| for a in x.OctopusServiceAddresses do
                                yield new XElement(XName.op_Implicit "Address", a) |]

    let state = let a,b = x.DeployingState in sprintf "%d,%d" a b
    
    let generals = [| new XElement(XName.op_Implicit "VmGuid", x.VmGuid.ToString());
                      new XElement(XName.op_Implicit "MachineName", x.MachineName);
                      new XElement(XName.op_Implicit "AdminPassword", x.AdminPassword);
                      new XElement(XName.op_Implicit "OwnerName", x.OwnerName);
                      new XElement(XName.op_Implicit "SecurityToken", x.SecurityToken.ToString());
                      new XElement(XName.op_Implicit "OctopusServiceAddresses", octopusAddresses) |]
    let generalConf = new XElement(XName.op_Implicit "GeneralConf", generals)
    let currentState = new XElement(XName.op_Implicit "CurrentState", state)

    let nodes = [| yield currentState; yield generalConf; for a in actions -> a.ToXmlElement() |]
    let vmConf = new XElement(XName.op_Implicit "VMConf", nodes)
    new XDocument(vmConf)  

  member x.ToXmlFile (path:string) =
    x.ToXmlDoc().Save(path)

  member x.ToXmlWriter (textWriter:System.IO.TextWriter) =
    x.ToXmlDoc().Save(textWriter)

  member x.ToByteArray_PlainXml () =
    use ms = new System.IO.MemoryStream()
    use sw = new System.IO.StreamWriter(ms)
    x.ToXmlWriter(sw)
    sw.Close()
    let arr = ms.ToArray()
    ms.Close()
    [| for b in arr do
         if b <> 13uy && b <> 10uy then    // skips \r and \n
           yield b |]

  member x.ToString_PlainXml () =
    let encoder = new System.Text.UTF8Encoding()
    encoder.GetString( x.ToByteArray_PlainXml () )
    
    
  static member FromXml (doc:XDocument) =
    let generalConf = doc.Root.Element(XName.op_Implicit "GeneralConf")
    let vmGuid = generalConf.Element(XName.op_Implicit "VmGuid").Value
    let machineName = generalConf.Element(XName.op_Implicit "MachineName").Value
    let ownerName = generalConf.Element(XName.op_Implicit "OwnerName").Value
    let adminPassword = generalConf.Element(XName.op_Implicit "AdminPassword").Value
    let securityToken = generalConf.Element(XName.op_Implicit "SecurityToken").Value
    let octopusAddresses = [| for el in (generalConf.Element(XName.op_Implicit "OctopusServiceAddresses").Elements()) do yield el.Value |]
    let data = new ConfigurationData (new System.Guid(vmGuid), machineName, ownerName, adminPassword, new System.Guid(securityToken), octopusAddresses)
    
    let s = doc.Root.Element(XName.op_Implicit "CurrentState").Value.Split([|','|])
    data.DeployingState <- ( System.Int32.Parse(s.[0]), System.Int32.Parse(s.[1]) )
    
    let acts = doc.Root.Elements(XName.op_Implicit "Action")
    for a in acts do
      let name = a.Attribute(XName.op_Implicit "Name").Value
      match name with
      | "DeployHpcNode" -> data.AddAction (DeployHpcNode.FromXmlNode(a))
      | _ -> failwithf "Unknown action: %s" name
    
    data

  
  static member FromXml (docstr:string) =
    let doc = XDocument.Parse(docstr)
    ConfigurationData.FromXml(doc)

  static member FromXmlFile (file:string) =
    let doc = XDocument.Load(file)
    ConfigurationData.FromXml(doc)

